<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="generator" content="VNote">

    <title>9_外部NoSQL数据库访问</title>
    <link rel="icon" href="https://github.com/tamlok/vnote/raw/master/src/resources/icons/vnote.ico">

    <style type="text/css">
    /* STYLE_GLOBAL_PLACE_HOLDER */
    </style>

    <style type="text/css">
    *,
*::before,
*::after {
  box-sizing: border-box;
}

.container-fluid {
    width: 100%;
    padding-right: 15px;
    padding-left: 15px;
    margin-right: auto;
    margin-left: auto;
}

.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto {
    position: relative;
    width: 100%;
    min-height: 1px;
    padding-right: 15px;
    padding-left: 15px;
}

.col-12 {
    -webkit-box-flex: 0;
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
}

@media (min-width: 768px) {
    .col-md-3 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 25%;
        flex: 0 0 25%;
        max-width: 25%;
    }
}

@media (min-width: 768px) {
    .col-md-9 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 75%;
        flex: 0 0 75%;
        max-width: 75%;
    }
}

@media (min-width: 1200px) {
    .col-xl-2 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 16.666667%;
        flex: 0 0 16.666667%;
        max-width: 16.666667%;
    }
}

@media (min-width: 1200px) {
    .col-xl-10 {
        -webkit-box-flex: 0;
        -ms-flex: 0 0 83.333333%;
        flex: 0 0 83.333333%;
        max-width: 83.333333%;
    }
}

@media (min-width: 768px) {
    .pt-md-3, .py-md-3 {
        padding-top: 1rem!important;
    }
}

@media (min-width: 768px) {
    .pb-md-3, .py-md-3 {
        padding-bottom: 1rem!important;
    }
}

@media (min-width: 768px) {
    .pl-md-5, .px-md-5 {
        padding-left: 3rem!important;
    }
}

.d-none {
    display: none!important;
}

@media (min-width: 1200px) {
    .d-xl-block {
        display: block!important;
    }
}

@media (min-width: 768px) {
    .d-md-block {
        display: block!important;
    }
}

.bd-content {
    -webkit-box-ordinal-group: 1;
    -ms-flex-order: 0;
    order: 0;
}

.bd-toc {
    position: -webkit-sticky;
    position: sticky;
    top: 4rem;
    height: calc(100vh - 10rem);
    overflow-y: auto;
}

.bd-toc {
    -webkit-box-ordinal-group: 2;
    -ms-flex-order: 1;
    order: 1;
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;
    font-size: .875rem;
}

.section-nav {
    padding-left: 0;
}

.section-nav ul {
    font-size: .875rem;
    list-style-type: none;
}

.section-nav li {
    font-size: .875rem;
}

.section-nav a {
    color: inherit !important;
}

.row {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -ms-flex-wrap: wrap;
    flex-wrap: wrap;
    margin-right: -15px;
    margin-left: -15px;
}

@media (min-width: 1200px) {
    .flex-xl-nowrap {
        flex-wrap: nowrap !important;
    }
}

#floating-button {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: #00897B;
    position: fixed;
    top: .5rem;
    right: .5rem;
    cursor: pointer;
    box-shadow: 0px 2px 5px #666;
}

#floating-button .more {
    color: #F5F5F5;
    position: absolute;
    top: 0;
    display: block;
    bottom: 0;
    left: 0;
    right: 0;
    text-align: center;
    padding: 0;
    margin: 0;
    line-height: 2.5rem;
    font-size: 2rem;
    font-family: 'monospace';
    font-weight: 300;
}

.hide-none {
    display: none !important;
}

.col-expand {
    -webkit-box-flex: 0;
    -ms-flex: 0 0 100% !important;
    flex: 0 0 100% !important;
    max-width: 100% !important;
    padding-right: 3rem !important;
}

.outline-bold {
    font-weight: bolder !important;
}

@media print {
    #floating-button {
        display: none !important;
    }
}

    @keyframes flash { 
  0% { color: rgb(128, 203, 196); }
  10% { color: rgb(0, 137, 123); }
  40% { color: rgb(0, 137, 123); }
  50% { color: rgb(128, 203, 196); }
  60% { color: rgb(0, 137, 123); }
  90% { color: rgb(0, 137, 123); }
}
.highlighted-anchor { animation: flash 1s; }
div.mark-rect { background: transparent; border: 5px solid rgb(87, 104, 196); border-radius: 2px; position: absolute; }
#vnote-footer { width: 100%; text-align: center; opacity: 0.2; margin-top: 3rem; }
#vnote-footer p { font-size: 0.8rem; }
#vnote-footer a { color: inherit !important; }
x-eqs { display: flex; flex-direction: row; align-content: space-between; align-items: center; }
x-eqs > x-eqn { width: 100%; margin-left: 3rem; }
x-eqs > span { text-align: right; }
.view-image, .view-svg { transition: 0.3s; }
.modal-box { display: none; position: fixed; z-index: 1000; padding-top: 50px; left: 0px; top: 0px; width: 100%; height: 100%; overflow: hidden; background-color: rgba(68, 68, 68, 0.952941); }
.modal-content { margin: auto; display: block; width: auto; height: auto; cursor: move; }
.modal-content { animation-name: zoom; animation-duration: 0.6s; }
@-webkit-keyframes zoom { 
  0% { transform: scale(0); }
  100% { transform: scale(1); }
}
@keyframes zoom { 
  0% { transform: scale(0); }
  100% { transform: scale(1); }
}
span.modal-close { position: absolute; z-index: 1000; top: 15px; right: 35px; color: rgb(218, 218, 218); font-size: 40px; font-weight: bold; transition: 0.3s; }
span.modal-close:hover, span.modal-close:focus { color: rgb(238, 238, 238); text-decoration: none; cursor: pointer; }
@media print {
  pre, pre code, td.hljs-ln-code { white-space: pre-wrap !important; word-break: break-all !important; }
  code, a { word-break: break-all !important; }
  div.flowchart-diagram, div.mermaid-diagram, div.plantuml-diagram { overflow: hidden !important; }
  img { max-width: 100% !important; height: auto !important; }
  #vnote-footer { display: none !important; }
}
.alert { position: relative; padding: 0.75rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.25rem; }
.alert-primary { color: rgb(0, 64, 133); background-color: rgb(204, 229, 255); border-color: rgb(184, 218, 255); }
.alert-secondary { color: rgb(56, 61, 65); background-color: rgb(226, 227, 229); border-color: rgb(214, 216, 219); }
.alert-success { color: rgb(21, 87, 36); background-color: rgb(212, 237, 218); border-color: rgb(195, 230, 203); }
.alert-info { color: rgb(12, 84, 96); background-color: rgb(209, 236, 241); border-color: rgb(190, 229, 235); }
.alert-warning { color: rgb(133, 100, 4); background-color: rgb(255, 243, 205); border-color: rgb(255, 238, 186); }
.alert-danger { color: rgb(114, 28, 36); background-color: rgb(248, 215, 218); border-color: rgb(245, 198, 203); }
.alert-light { color: rgb(129, 129, 130); background-color: rgb(254, 254, 254); border-color: rgb(253, 253, 254); }
.alert-dark { color: rgb(27, 30, 33); background-color: rgb(214, 216, 217); border-color: rgb(198, 200, 202); }
.vnote-anchor { font-weight: 400; color: rgba(0, 123, 255, 0.498039); transition: color 0.16s linear; padding-left: 0.375em; -webkit-font-smoothing: antialiased; text-decoration: none; opacity: 0; }
.vnote-anchor:hover { color: rgb(0, 123, 255); text-decoration: none; opacity: 1; }
.vnote-anchor::after { content: attr(data-anchor-icon); }
.vnote-btn { position: relative; display: inline-block; padding: 6px 12px; font-size: 13px; font-weight: 700; line-height: 20px; white-space: nowrap; vertical-align: middle; cursor: pointer; border: none; user-select: none; -webkit-appearance: none; }
.vnote-copy-clipboard-btn { transition: opacity 0.3s ease-in-out; opacity: 0; padding: 2px 6px; position: absolute; top: 5px; right: 5px; }
pre:hover .vnote-copy-clipboard-btn { opacity: 1; }
pre.vnote-snippet { position: relative; }
body { margin: 0px auto; font-family: "Segoe UI", Helvetica, sans-serif, Tahoma, Arial, Geneva, Georgia, Palatino, "Times New Roman", "Hiragino Sans GB", 冬青黑体, "Microsoft YaHei", 微软雅黑, "Microsoft YaHei UI", "WenQuanYi Micro Hei", 文泉驿雅黑, Dengxian, 等线体, STXihei, 华文细黑, "Liberation Sans", "Droid Sans", NSimSun, 新宋体, SimSun, 宋体; color: rgb(34, 34, 34); line-height: 1.5; padding: 15px; background: rgb(238, 238, 238); font-size: 16px; }
h1, h2, h3, h4, h5, h6 { color: rgb(34, 34, 34); font-weight: bold; margin-top: 20px; margin-bottom: 10px; padding: 0px; }
p { padding: 0px; margin-top: 16px; margin-bottom: 16px; }
h1 { font-size: 26px; }
h2 { font-size: 24px; }
h3 { font-size: 22px; }
h4 { font-size: 20px; }
h5 { font-size: 19px; }
h6 { font-size: 18px; }
a { color: rgb(0, 153, 255); margin: 0px; padding: 0px; vertical-align: baseline; text-decoration: none; word-break: break-word; }
a:hover { text-decoration: underline; color: rgb(255, 102, 0); }
a:visited { color: purple; }
ul, ol { padding: 0px 0px 0px 24px; }
li { line-height: 24px; }
li ul, li ol { margin-left: 16px; }
p, ul, ol { font-size: 16px; line-height: 24px; }
pre { display: block; overflow-y: hidden; overflow-x: auto; tab-size: 4; }
code { font-family: Consolas, Monaco, monospace, Courier; color: rgb(142, 36, 170); word-break: break-word; }
pre code { display: block; overflow-x: auto; padding: 0.5em; color: rgb(34, 34, 34); background-color: rgb(224, 224, 224); border-left: 0.5em solid rgb(0, 137, 123); line-height: 1.5; font-family: Consolas, Monaco, monospace, Courier; white-space: pre; tab-size: 4; }
pre code.markdown-metadata { border-left: 0.5em solid rgb(128, 203, 196); }
aside { display: block; float: right; width: 390px; }
blockquote { color: rgb(102, 102, 102); border-left: 0.5em solid rgb(122, 122, 122); padding: 0px 1em; margin-left: 0px; }
blockquote p { color: rgb(102, 102, 102); }
hr { display: block; text-align: left; margin: 1em 0px; border: none; height: 2px; background: rgb(153, 153, 153); }
table { padding: 0px; margin: 1rem 0.5rem; border-collapse: collapse; }
table tr { border-top: 2px solid rgb(204, 204, 204); background-color: white; margin: 0px; padding: 0px; }
table tr:nth-child(2n) { background-color: rgb(248, 248, 248); }
table tr th { font-weight: bold; border: 2px solid rgb(204, 204, 204); margin: 0px; padding: 6px 13px; }
table tr td { border: 2px solid rgb(204, 204, 204); margin: 0px; padding: 6px 13px; }
table tr th :first-child, table tr td :first-child { margin-top: 0px; }
table tr th :last-child, table tr td :last-child { margin-bottom: 0px; }
div.mermaid-diagram { margin: 16px 0px; overflow-y: hidden; }
div.flowchart-diagram { padding: 0px 5px; margin: 16px 0px; width: fit-content; overflow: hidden; }
div.wavedrom-diagram { padding: 0px 5px; margin: 16px 0px; width: fit-content; overflow: hidden; }
div.plantuml-diagram { padding: 5px 5px 0px; margin: 16px 0px; width: fit-content; overflow: hidden; }
.img-package { text-align: center; }
img.img-center { display: block; margin-left: auto; margin-right: auto; }
span.img-caption { min-width: 20%; max-width: 80%; display: inline-block; padding: 10px; margin: 0px auto; border-bottom: 1px solid rgb(192, 192, 192); color: rgb(108, 108, 108); text-align: center; line-height: 1.5; }
.emoji_zero, .emoji_one, .emoji_two, .emoji_three, .emoji_four, .emoji_five, .emoji_six, .emoji_seven, .emoji_eight, .emoji_nine { margin-left: 5px; margin-right: 8px; }
div.preview-hint { opacity: 0.5; margin-top: 30%; margin-bottom: 30%; align-items: center; display: flex; flex-direction: column; justify-content: center; }
table.hljs-ln tr { border: none; background-color: transparent; }
table.hljs-ln tr td { border: none; background-color: transparent; }
table.hljs-ln tr td.hljs-ln-numbers { user-select: none; text-align: center; color: rgb(170, 170, 170); border-right: 1px solid rgb(204, 204, 204); vertical-align: top; padding-right: 5px; white-space: nowrap; }
table.hljs-ln tr td.hljs-ln-code { padding-left: 10px; }
::-webkit-scrollbar { background-color: rgb(234, 234, 234); width: 14px; height: 14px; border: none; }
::-webkit-scrollbar-corner { background-color: rgb(234, 234, 234); }
::-webkit-scrollbar-button { height: 14px; width: 14px; background-color: rgb(234, 234, 234); }
::-webkit-scrollbar-button:hover { background-color: rgb(208, 208, 208); }
::-webkit-scrollbar-button:active { background-color: rgb(178, 178, 178); }
::-webkit-scrollbar-track { background-color: rgb(234, 234, 234); }
::-webkit-scrollbar-thumb { border: none; background-color: rgb(218, 218, 218); }
::-webkit-scrollbar-thumb:hover { background-color: rgb(208, 208, 208); }
::-webkit-scrollbar-thumb:active { background-color: rgb(178, 178, 178); }
::-webkit-scrollbar-button:horizontal:increment { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="rotate(-90 256.00000000000006,256) " id="svg_1">   <polygon fill="%23333333" id="svg_2" points="128,192 256,320 384,192  "/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::-webkit-scrollbar-button:horizontal:decrement { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="rotate(90 255.99999999999997,256.00000000000006) " id="svg_1">   <polygon points="128,192 256,320 384,192  " id="svg_2" fill="%23333333"/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::-webkit-scrollbar-button:vertical:increment { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="null" id="svg_1">   <polygon points="128,192 256,320 384,192  " id="svg_2" fill="%23333333"/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::-webkit-scrollbar-button:vertical:decrement { background-image: url('data:image/svg+xml;utf8,<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <g>    <g transform="rotate(180 255.99999999999997,256) " id="svg_1">   <polygon points="128,192 256,320 384,192  " id="svg_2" fill="%23333333"/>  </g> </g></svg>'); background-repeat: no-repeat; background-size: contain; }
::selection { background: rgb(25, 118, 210); color: rgb(238, 238, 238); }
.modal-box { background-color: rgba(234, 234, 234, 0.952941); }
span.modal-close { color: rgb(102, 102, 102); }
span.modal-close:hover, span.modal-close:focus { color: rgb(34, 34, 34); }
.hljs { display: block; overflow-x: auto; padding: 0.5em; background: rgb(224, 224, 224); }
.hljs, .hljs-subst { color: rgb(54, 54, 54); }
.hljs-comment { color: rgb(118, 118, 118); }
.hljs-keyword, .hljs-attribute, .hljs-selector-tag, .hljs-meta-keyword, .hljs-doctag, .hljs-name { color: rgb(0, 0, 238); }
.hljs-type, .hljs-string, .hljs-number, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion { color: rgb(136, 0, 0); }
.hljs-title, .hljs-section { color: rgb(136, 0, 0); font-weight: bold; }
.hljs-regexp, .hljs-symbol, .hljs-variable, .hljs-template-variable, .hljs-link, .hljs-selector-attr, .hljs-selector-pseudo { color: rgb(188, 96, 96); }
.hljs-literal { color: rgb(175, 0, 215); }
.hljs-built_in, .hljs-bullet, .hljs-code, .hljs-addition { color: rgb(0, 135, 0); }
.hljs-meta { color: rgb(31, 113, 153); }
.hljs-meta-string { color: rgb(77, 153, 191); }
.hljs-emphasis { font-style: italic; }
.hljs-strong { font-weight: bold; }
.mermaid-diagram .mermaid .label { color: rgb(51, 51, 51); }
.mermaid-diagram .node rect, .mermaid-diagram .node circle, .mermaid-diagram .node ellipse, .mermaid-diagram .node polygon { fill: rgb(236, 236, 255); stroke: rgb(204, 204, 255); stroke-width: 1px; }
.mermaid-diagram .edgePath .path { stroke: rgb(51, 51, 51); }
.mermaid-diagram .edgeLabel { background-color: rgb(232, 232, 232); }
.mermaid-diagram .cluster rect { fill: rgb(255, 255, 222) !important; rx: 4 !important; stroke: rgb(170, 170, 51) !important; stroke-width: 1px !important; }
.mermaid-diagram .cluster text { fill: rgb(51, 51, 51); }
.mermaid-diagram .actor { stroke: rgb(204, 204, 255); fill: rgb(236, 236, 255); }
.mermaid-diagram text.actor { fill: black; stroke: none; }
.mermaid-diagram .actor-line { stroke: grey; }
.mermaid-diagram .messageLine0 { stroke-width: 1.5; stroke: rgb(51, 51, 51); }
.mermaid-diagram .messageLine1 { stroke-width: 1.5; stroke: rgb(51, 51, 51); }
.mermaid-diagram #arrowhead { fill: rgb(51, 51, 51); }
.mermaid-diagram #crosshead path { fill: rgb(51, 51, 51) !important; stroke: rgb(51, 51, 51) !important; }
.mermaid-diagram .messageText { fill: rgb(51, 51, 51); stroke: none; }
.mermaid-diagram .labelBox { stroke: rgb(204, 204, 255); fill: rgb(236, 236, 255); }
.mermaid-diagram .labelText { fill: black; stroke: none; }
.mermaid-diagram .loopText { fill: black; stroke: none; }
.mermaid-diagram .loopLine { stroke-width: 2; stroke: rgb(204, 204, 255); }
.mermaid-diagram .note { stroke: rgb(170, 170, 51); fill: rgb(255, 245, 173); }
.mermaid-diagram .noteText { fill: black; stroke: none; font-family: "trebuchet ms", verdana, arial; font-size: 14px; }
.mermaid-diagram .section { stroke: none; opacity: 0.2; }
.mermaid-diagram .section0 { fill: rgba(102, 102, 255, 0.490196); }
.mermaid-diagram .section2 { fill: rgb(255, 244, 0); }
.mermaid-diagram .section1, .mermaid-diagram .section3 { fill: white; opacity: 0.2; }
.mermaid-diagram .sectionTitle0 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle1 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle2 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle3 { fill: rgb(51, 51, 51); }
.mermaid-diagram .sectionTitle { text-anchor: start; font-size: 11px; }
.mermaid-diagram .grid .tick { stroke: lightgrey; opacity: 0.3; shape-rendering: crispEdges; }
.mermaid-diagram .grid path { stroke-width: 0; }
.mermaid-diagram .today { fill: none; stroke: red; stroke-width: 2px; }
.mermaid-diagram .task { stroke-width: 2; }
.mermaid-diagram .taskText { text-anchor: middle; font-size: 11px; }
.mermaid-diagram .taskTextOutsideRight { fill: black; text-anchor: start; font-size: 11px; }
.mermaid-diagram .taskTextOutsideLeft { fill: black; text-anchor: end; font-size: 11px; }
.mermaid-diagram .taskText0, .mermaid-diagram .taskText1, .mermaid-diagram .taskText2, .mermaid-diagram .taskText3 { fill: white; }
.mermaid-diagram .task0, .mermaid-diagram .task1, .mermaid-diagram .task2, .mermaid-diagram .task3 { fill: rgb(138, 144, 221); stroke: rgb(83, 79, 188); }
.mermaid-diagram .taskTextOutside0, .mermaid-diagram .taskTextOutside2 { fill: black; }
.mermaid-diagram .taskTextOutside1, .mermaid-diagram .taskTextOutside3 { fill: black; }
.mermaid-diagram .active0, .mermaid-diagram .active1, .mermaid-diagram .active2, .mermaid-diagram .active3 { fill: rgb(191, 199, 255); stroke: rgb(83, 79, 188); }
.mermaid-diagram .activeText0, .mermaid-diagram .activeText1, .mermaid-diagram .activeText2, .mermaid-diagram .activeText3 { fill: black !important; }
.mermaid-diagram .done0, .mermaid-diagram .done1, .mermaid-diagram .done2, .mermaid-diagram .done3 { stroke: grey; fill: lightgrey; stroke-width: 2; }
.mermaid-diagram .doneText0, .mermaid-diagram .doneText1, .mermaid-diagram .doneText2, .mermaid-diagram .doneText3 { fill: black !important; }
.mermaid-diagram .crit0, .mermaid-diagram .crit1, .mermaid-diagram .crit2, .mermaid-diagram .crit3 { stroke: rgb(255, 136, 136); fill: red; stroke-width: 2; }
.mermaid-diagram .activeCrit0, .mermaid-diagram .activeCrit1, .mermaid-diagram .activeCrit2, .mermaid-diagram .activeCrit3 { stroke: rgb(255, 136, 136); fill: rgb(191, 199, 255); stroke-width: 2; }
.mermaid-diagram .doneCrit0, .mermaid-diagram .doneCrit1, .mermaid-diagram .doneCrit2, .mermaid-diagram .doneCrit3 { stroke: rgb(255, 136, 136); fill: lightgrey; stroke-width: 2; cursor: pointer; shape-rendering: crispEdges; }
.mermaid-diagram .doneCritText0, .mermaid-diagram .doneCritText1, .mermaid-diagram .doneCritText2, .mermaid-diagram .doneCritText3 { fill: black !important; }
.mermaid-diagram .activeCritText0, .mermaid-diagram .activeCritText1, .mermaid-diagram .activeCritText2, .mermaid-diagram .activeCritText3 { fill: black !important; }
.mermaid-diagram .titleText { text-anchor: middle; font-size: 18px; fill: black; }
.mermaid-diagram .node text { font-family: "trebuchet ms", verdana, arial; font-size: 14px; }
.mermaid-diagram div.mermaidTooltip { position: absolute; text-align: center; max-width: 200px; padding: 2px; font-family: "trebuchet ms", verdana, arial; font-size: 12px; background: rgb(255, 255, 222); border: 1px solid rgb(170, 170, 51); border-radius: 2px; pointer-events: none; z-index: 100; }
#mermaid-diagram-1 .node > rect { }
#mermaid-diagram-1 .node text { fill: rgb(0, 0, 0); stroke: none; font-weight: 300; font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; font-size: 14px; }
#mermaid-diagram-1 .edgeLabel text { fill: rgb(0, 0, 0); stroke: none; font-weight: 300; font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; font-size: 14px; }
#mermaid-diagram-1 .cluster rect { fill: rgb(255, 255, 222); rx: 4px; stroke: rgb(170, 170, 51); stroke-width: 1px; }
#mermaid-diagram-1 .cyan > rect, .cyan > polygon, .cyan > circle, .cyan > ellipse { fill: rgb(153, 255, 255); stroke: rgb(255, 255, 255); }
#mermaid-diagram-2 .node > rect { }
#mermaid-diagram-2 .node text { fill: rgb(0, 0, 0); stroke: none; font-weight: 300; font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; font-size: 14px; }
#mermaid-diagram-2 .edgeLabel text { fill: rgb(0, 0, 0); stroke: none; font-weight: 300; font-family: "Helvetica Neue", Helvetica, Arial, sans-serf; font-size: 14px; }
#mermaid-diagram-2 .cluster rect { fill: rgb(255, 255, 222); rx: 4px; stroke: rgb(170, 170, 51); stroke-width: 1px; }
#mermaid-diagram-2 .cyan > rect, .cyan > polygon, .cyan > circle, .cyan > ellipse { fill: rgb(153, 255, 255); stroke: rgb(255, 255, 255); }

    </style>

    <script type="text/javascript">
var toc = [];

var setVisible = function(node, visible) {
    var cl = 'hide-none';
    if (visible) {
        node.classList.remove(cl);
    } else {
        node.classList.add(cl);
    }
};

var isVisible = function(node) {
    var cl = 'hide-none';
    return !node.classList.contains(cl);
};

var setPostContentExpanded = function(node, expanded) {
    var cl = 'col-expand';
    if (expanded) {
        node.classList.add(cl);
    } else {
        node.classList.remove(cl);
    }
};

var setOutlinePanelVisible = function(visible) {
    var outlinePanel = document.getElementById('outline-panel');
    var postContent = document.getElementById('post-content');

    setVisible(outlinePanel, visible);
    setPostContentExpanded(postContent, !visible);
};

var isOutlinePanelVisible = function() {
    var outlinePanel = document.getElementById('outline-panel');
    return isVisible(outlinePanel);
};

window.addEventListener('load', function() {
    var outlinePanel = document.getElementById('outline-panel');
    outlinePanel.style.display = 'initial';

    var floatingContainer = document.getElementById('container-floating');
    floatingContainer.style.display = 'initial';

    var outlineContent = document.getElementById('outline-content');
    var postContent = document.getElementById('post-content');

    // Escape @text to Html.
    var escapeHtml = function(text) {
        var map = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#039;'
        };

        return text.replace(/[&<>"']/g, function(m) { return map[m]; });
    }

    // Fetch the outline.
    var headers = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");
    toc = [];
    for (var i = 0; i < headers.length; ++i) {
        var header = headers[i];

        toc.push({
            level: parseInt(header.tagName.substr(1)),
            anchor: header.id,
            title: escapeHtml(header.textContent)
        });
    }

    if (toc.length == 0) {
        setOutlinePanelVisible(false);
        setVisible(floatingContainer, false);
        return;
    }

    var baseLevel = baseLevelOfToc(toc);
    var tocTree = tocToTree(toPerfectToc(toc, baseLevel), baseLevel);

    outlineContent.innerHTML = tocTree;
    setOutlinePanelVisible(true);
    setVisible(floatingContainer, true);
});

// Return the topest level of @toc, starting from 1.
var baseLevelOfToc = function(p_toc) {
    var level = -1;
    for (i in p_toc) {
        if (level == -1) {
            level = p_toc[i].level;
        } else if (level > p_toc[i].level) {
            level = p_toc[i].level;
        }
    }

    if (level == -1) {
        level = 1;
    }

    return level;
};

// Handle wrong title levels, such as '#' followed by '###'
var toPerfectToc = function(p_toc, p_baseLevel) {
    var i;
    var curLevel = p_baseLevel - 1;
    var perfToc = [];
    for (i in p_toc) {
        var item = p_toc[i];

        // Insert empty header.
        while (item.level > curLevel + 1) {
            curLevel += 1;
            var tmp = { level: curLevel,
                        anchor: '',
                        title: '[EMPTY]'
                      };
            perfToc.push(tmp);
        }

        perfToc.push(item);
        curLevel = item.level;
    }

    return perfToc;
};

var itemToHtml = function(item) {
    return '<a href="#' + item.anchor + '" data="' + item.anchor + '">' + item.title + '</a>';
};

// Turn a perfect toc to a tree using <ul>
var tocToTree = function(p_toc, p_baseLevel) {
    var i;
    var front = '<li>';
    var ending = ['</li>'];
    var curLevel = p_baseLevel;
    for (i in p_toc) {
        var item = p_toc[i];
        if (item.level == curLevel) {
            front += '</li>';
            front += '<li>';
            front += itemToHtml(item);
        } else if (item.level > curLevel) {
            // assert(item.level - curLevel == 1)
            front += '<ul>';
            ending.push('</ul>');
            front += '<li>';
            front += itemToHtml(item);
            ending.push('</li>');
            curLevel = item.level;
        } else {
            while (item.level < curLevel) {
                var ele = ending.pop();
                front += ele;
                if (ele == '</ul>') {
                    curLevel--;
                }
            }
            front += '</li>';
            front += '<li>';
            front += itemToHtml(item);
        }
    }
    while (ending.length > 0) {
        front += ending.pop();
    }
    front = front.replace("<li></li>", "");
    front = '<ul>' + front + '</ul>';
    return front;
};

var toggleMore = function() {
    if (toc.length == 0) {
        return;
    }

    var p = document.getElementById('floating-more');
    if (isOutlinePanelVisible()) {
        p.textContent = '<';
        setOutlinePanelVisible(false);
    } else {
        p.textContent = '>';
        setOutlinePanelVisible(true);
    }
};

window.addEventListener('scroll', function() {
    if (toc.length == 0 || !isOutlinePanelVisible()) {
        return;
    }

    var postContent = document.getElementById('post-content');
    var scrollTop = document.documentElement.scrollTop
                    || document.body.scrollTop
                    || window.pageYOffset;
    var eles = postContent.querySelectorAll("h1, h2, h3, h4, h5, h6");

    if (eles.length == 0) {
        return;
    }

    var idx = -1;
    var biaScrollTop = scrollTop + 50;
    for (var i = 0; i < eles.length; ++i) {
        if (biaScrollTop >= eles[i].offsetTop) {
            idx = i;
        } else {
            break;
        }
    }

    var header = '';
    if (idx != -1) {
        header = eles[idx].id;
    }

    highlightItemOnlyInOutline(header);
});

var highlightItemOnlyInOutline = function(id) {
    var cl = 'outline-bold';
    var outlineContent = document.getElementById('outline-content');
    var eles = outlineContent.querySelectorAll("a");
    var target = null;
    for (var i = 0; i < eles.length; ++i) {
        var ele = eles[i];
        if (ele.getAttribute('data') == id) {
            target = ele;
            ele.classList.add(cl);
        } else {
            ele.classList.remove(cl);
        }
    }

    // TODO: scroll target into view within the outline panel scroll area.
};

</script>


<!-- HEAD_PLACE_HOLDER -->
</head>
<body>
<div class="container-fluid">
<div class="row flex-xl-nowrap">
    <div id="outline-panel" style="display:none;" class="d-none d-md-block d-xl-block col-md-3 col-xl-2 bd-toc">
        <div id="outline-content" class="section-nav"></div>
    </div>
    <div id="post-content" class="col-12 col-md-9 col-xl-10 py-md-3 pl-md-5 bd-content">
    <div style="page-break-after: always;"></div>
<h1 id="toc_0">9. 外部NoSQL数据库访问<a class="vnote-anchor" href="#toc_0" data-anchor-icon="#"></a></h1>
<p><img src='' alt="" class="view-image"></p>
<p>  我们的ORM RESTful框架不仅能够通过<a href="">SynDB直接RDBMS访问</a>常规的SQL数据库引擎，还能够访问NoSQL引擎，参见<a href="">NoSQL和对象文档映射（ODM）</a>。</p>
<p>  还记得介绍mORMot数据库层的图：</p>
<div class="mermaid-diagram"><svg id="mermaid-diagram-1" xmlns="http://www.w3.org/2000/svg" height="100%" viewBox="0 0 694.25 604" style="max-width:694.25px;"><style type="text/css" title="mermaid-svg-internal-css">/*  */
#mermaid-diagram-1 .node&gt;rect { ; }
#mermaid-diagram-1 .node text  { fill:#000; stroke:none; font-weight:300; font-family:"Helvetica Neue",Helvetica,Arial,sans-serf; font-size:14px; }
#mermaid-diagram-1 .edgeLabel text  { fill:#000; stroke:none; font-weight:300; font-family:"Helvetica Neue",Helvetica,Arial,sans-serf; font-size:14px; }
#mermaid-diagram-1 .cluster rect  { rx:4px; fill: rgb(255, 255, 222); rx: 4px; stroke: rgb(170, 170, 51); stroke-width: 1px; }
#mermaid-diagram-1 .cyan&gt;rect, .cyan&gt;polygon, .cyan&gt;circle, .cyan&gt;ellipse { fill:#9ff;  stroke:#fff; }
/*  */
</style><g><g class="output"><g class="clusters"><g class="cluster" id="subGraph0" transform="translate(337.125,339)" style="opacity: 1;"><rect width="634.25" height="118" x="-317.125" y="-59"></rect><g class="label"><g transform="translate(0,0)"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"></div></foreignObject></g></g><text x="0" y="-45" fill="black" stroke="none" id="mermaid-diagram-1Text" style="text-anchor: middle;"> </text></g></g><g class="edgePaths"><g class="edgePath" style="opacity: 1;"><path class="path" d="M364.75,82.73809523809524L427.25,125L427.25,184L427.25,243L427.25,280L427.25,305" marker-end="url(#arrowhead60)" style="fill:none"></path><defs><marker id="arrowhead60" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M364.75,66.11847389558233L571.25,125L571.25,184L571.25,243L571.25,280L571.25,305" marker-end="url(#arrowhead61)" style="fill:none"></path><defs><marker id="arrowhead61" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M279.75,66.5598335067638L82,125L82,184L82,243L82,280L91.32203389830508,305" marker-end="url(#arrowhead62)" style="fill:none"></path><defs><marker id="arrowhead62" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M279.75,75.51515151515152L182,125L182,162" marker-end="url(#arrowhead63)" style="fill:none"></path><defs><marker id="arrowhead63" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M170.0677966101695,206L150,243L150,280L130.5084745762712,305" marker-end="url(#arrowhead64)" style="fill:none"></path><defs><marker id="arrowhead64" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M193.9322033898305,206L214,243L214,280L240.80084745762713,305" marker-end="url(#arrowhead65)" style="fill:none"></path><defs><marker id="arrowhead65" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M322.25,88L322.25,125L322.25,184L322.25,243L322.25,280L303.18220338983053,305" marker-end="url(#arrowhead66)" style="fill:none"></path><defs><marker id="arrowhead66" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M223.25,366.1727078891258L160,398L160,447L160,496" marker-end="url(#arrowhead67)" style="fill:none"></path><defs><marker id="arrowhead67" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M331.25,356.8487394957983L455.75,398L455.75,447L455.75,496" marker-end="url(#arrowhead68)" style="fill:none"></path><defs><marker id="arrowhead68" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(427.25,184)" style="opacity: 1;"><g transform="translate(-20,-12)" class="label"><foreignObject width="40" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">direct</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(571.25,184)" style="opacity: 1;"><g transform="translate(-20,-12)" class="label"><foreignObject width="40" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">direct</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(82,184)" style="opacity: 1;"><g transform="translate(-20,-12)" class="label"><foreignObject width="40" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">direct</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(182,125)" style="opacity: 1;"><g transform="translate(-20,-12)" class="label"><foreignObject width="40" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">direct</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(150,243)" style="opacity: 1;"><g transform="translate(-22,-12)" class="label"><foreignObject width="44" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">virtual</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(214,243)" style="opacity: 1;"><g transform="translate(-22,-12)" class="label"><foreignObject width="44" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">virtual</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(322.25,184)" style="opacity: 1;"><g transform="translate(-20,-12)" class="label"><foreignObject width="40" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">direct</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(160,447)" style="opacity: 1;"><g transform="translate(-20,-12)" class="label"><foreignObject width="40" height="24"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">direct</span></div></foreignObject></g></g><g class="edgeLabel" transform="translate(455.75,447)" style="opacity: 1;"><g transform="translate(-32,-24)" class="label"><foreignObject width="64" height="48"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel">DB.pas<br>TDataSet</span></div></foreignObject></g></g></g><g class="nodes"><g class="node cyan" id="C1" transform="translate(104,339)" style="opacity: 1;"><rect rx="0" ry="0" x="-49" y="-34" width="98" height="68"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-39,-24)"><foreignObject width="78" height="48"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">TObjectList<br>NoSQL</div></foreignObject></g></g></g><g class="node cyan" id="C2" transform="translate(277.25,339)" style="opacity: 1;"><rect rx="0" ry="0" x="-54" y="-34" width="108" height="68"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-44,-24)"><foreignObject width="88" height="48"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">External SQL<br>RDBMS</div></foreignObject></g></g></g><g class="node cyan" id="C3" transform="translate(427.25,339)" style="opacity: 1;"><rect rx="0" ry="0" x="-46" y="-34" width="92" height="68"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-36,-24)"><foreignObject width="72" height="48"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">MongoDB<br>NoSQL</div></foreignObject></g></g></g><g class="node cyan" id="C4" transform="translate(571.25,339)" style="opacity: 1;"><rect rx="0" ry="0" x="-48" y="-34" width="96" height="68"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-38,-24)"><foreignObject width="76" height="48"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">TSQLRest<br>redirection</div></foreignObject></g></g></g><g class="node cyan" id="A" transform="translate(322.25,54)" style="opacity: 1;"><rect rx="0" ry="0" x="-42.5" y="-34" width="85" height="68"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-32.5,-24)"><foreignObject width="65" height="48"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">mORMot<br>ORM</div></foreignObject></g></g></g><g class="node cyan" id="B" transform="translate(182,184)" style="opacity: 1;"><rect rx="0" ry="0" x="-37" y="-22" width="74" height="44"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-27,-12)"><foreignObject width="54" height="24"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">SQLite3</div></foreignObject></g></g></g><g class="node cyan" id="D1" transform="translate(160,530)" style="opacity: 1;"><rect rx="0" ry="0" x="-85.5" y="-34" width="171" height="68"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-75.5,-24)"><foreignObject width="151" height="48"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">Oracle SQLite3 ODBC<br>OleDB ZDBC</div></foreignObject></g></g></g><g class="node cyan" id="D2" transform="translate(455.75,530)" style="opacity: 1;"><rect rx="0" ry="0" x="-99" y="-34" width="198" height="68"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-89,-24)"><foreignObject width="178" height="48"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">FireDAC AnyDAC UniDAC<br>BDE DBExpress NexusDB</div></foreignObject></g></g></g></g></g></g></svg></div>
<p>  可以从mORMot的对象文档映射（ODM）功能访问以下NoSQL引擎：</p>
<table>
<thead>
<tr>
<th>NoSQL引擎</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>TObjectList</td>
<td>内存存储，具有JSON或二进制磁盘持久性</td>
</tr>
<tr>
<td>MongoDB</td>
<td>排名第一的NoSQL数据库引擎</td>
</tr>
</tbody>
</table>
<p>  实际上，我们可以考虑将<code>TSQLRestStorageInMemory</code>实例及其<code>TObjectList</code>存储作为一个NoSQL高速内存引擎，用纯Delphi编写。有关此特性的详细信息，请参见<a href="">内存中的“静态”进程</a>。</p>
<p>  MongoDB（源自“humongous”）是一个跨平台的面向文档的数据库系统，当然也是最著名的NoSQL数据库。</p>
<p>  根据http://db-engines.com 2015年12月的数据，MongoDB在最流行的数据库管理系统中排名第四，NoSQL数据库管理系统排名第一。</p>
<p>  我们的mORMot提供了对该数据库的高级访问，具有完整的NoSQL和对象文档映射（ODM）功能。</p>
<p>  分两个层次进行了集成：</p>
<ul>
<li>通过<code>SynMongoDB.pas</code>单元直接底层访问MongoDB服务器；</li>
<li>通过<code>mORMotMongoDB.pas</code>单元与ORM紧密集成（实际成为了ODM）。</li>
</ul>
<p>  MongoDB绕过了传统的基于表的关系数据库结构，而是支持具有动态模式的JSON类文档（MongoDB称之为BSON格式），这完全符合mORMot的RESTful方法。</p>
<h2 id="toc_1">9.1. SynMongoDB客户端<a class="vnote-anchor" href="#toc_1" data-anchor-icon="#"></a></h2>
<p>  <code>SynMongoDB.pas</code>单元可直接访问MongoDB服务器并进行了优化。</p>
<p>  它可以访问任何BSON数据，包括文档、数组和MongoDB的自定义类型（如ObjectID、date、binary、regex、Decimal128或Javascript）：</p>
<ul>
<li>如使用<code>TBSONObjectID</code>在客户端创建一些真正的文档标识符（MongoDB不会为您生成ID：通常是在客户端生成唯一ID）；</li>
<li>从任何Delphi类型生成BSON内容（通过<code>TBSONWriter</code>）；</li>
<li>BSON流的快速原地解析，无需任何内存分配（通过TBSONElement）；</li>
<li>使用<code>TBSONVariant</code>自定义变体类型存储MongoDB的自定义类型值；</li>
<li>使用<code>SynCommons.pas</code>的<a href="">TDocVariant自定义变体类型</a>作为文档存储和后期绑定访问；</li>
<li>使用MongoDB扩展语法处理其自定义类型，支持BSON与JSON编码的相互转换。</li>
</ul>
<p>  该单元定义了一些对象，这些对象能够连接和管理任何MongoDB服务机群上的数据库和文档集合：</p>
<ul>
<li>通过<code>TMongoClient</code>类连接到一个或多个服务器，包括辅助主机；</li>
<li>通过<code>TMongoDatabase</code>类访问任何数据库实例；</li>
<li>通过<code>TMongoCollection</code>类访问任何集合；</li>
<li>具有速度方面的优势，比如批量插入或删除模式，以及显式设置为写关注。</li>
</ul>
<p>  在集合级，您可以直接访问数据，使用<code>TDocVariant</code>/<code>TBSONVariant</code>等高级结构，使用易于阅读的JSON或底层BSON内容。</p>
<p>  您还可以在很多方面优化客户端流程，例如关于错误处理或写回执（即如何确认远程数据的修改）。</p>
<h3 id="toc_2">9.1.1. 连接到服务端<a class="vnote-anchor" href="#toc_2" data-anchor-icon="#"></a></h3>
<p>  下面是一些示例代码，它能够连接到MongoDB服务器，并返回服务器时间：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> Client: TMongoClient;
    DB: TMongoDatabase;
    serverTime: TDateTime;
    res: variant; <span class="hljs-comment">// we will return the command result as TDocVariant</span>
    errmsg: RawUTF8;
<span class="hljs-keyword">begin</span>
  Client := TMongoClient.Create(<span class="hljs-string">'localhost'</span>,<span class="hljs-number">27017</span>);
  <span class="hljs-keyword">try</span>
    DB := Client.Database[<span class="hljs-string">'mydb'</span>];
    writeln(<span class="hljs-string">'Connecting to '</span>,DB.<span class="hljs-keyword">Name</span>); <span class="hljs-comment">// will write 'mydb'</span>
    errmsg := DB.RunCommand(<span class="hljs-string">'hostInfo'</span>,res); <span class="hljs-comment">// run a command</span>
    <span class="hljs-keyword">if</span> errmsg&lt;&gt;<span class="hljs-string">''</span> <span class="hljs-keyword">then</span>
      <span class="hljs-keyword">exit</span>; <span class="hljs-comment">// quit on any error</span>
    serverTime := res.system.currentTime; <span class="hljs-comment">// direct conversion to TDateTime</span>
    writeln(<span class="hljs-string">'Server time is '</span>,DateTimeToStr(serverTime));
  <span class="hljs-keyword">finally</span>
    Client.Free; <span class="hljs-comment">// will release the DB instance</span>
  <span class="hljs-keyword">end</span>;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  注意，对于这个底层命令，我们使用了<code>TDocVariant</code>及其后期绑定功能。</p>
<p>  如果在调试期间将鼠标放在res变量上，您将看到以下JSON内容：</p>
<pre><code class="lang-json hljs">{<span class="hljs-attr">"system"</span>:{<span class="hljs-attr">"currentTime"</span>:<span class="hljs-string">"2014-05-06T15:24:25"</span>,<span class="hljs-attr">"hostname"</span>:<span class="hljs-string">"Acer"</span>,<span class="hljs-attr">"cpuAddrSize"</span>:<span class="hljs-number">64</span>,<span class="hljs-attr">"memSizeMB"</span>:<span class="hljs-number">3934</span>,<span class="hljs-attr">"numCores"</span>:<span class="hljs-number">4</span>,<span class="hljs-attr">"cpuArch"</span>:<span class="hljs-string">"x86_64"</span>,<span class="hljs-attr">"numaEnabled"</span>:<span class="hljs-literal">false</span>},<span class="hljs-attr">"os"</span>:{<span class="hljs-attr">"type"</span>:<span class="hljs-string">"Windows"</span>,<span class="hljs-attr">"name"</span>:<span class="hljs-string">"Microsoft Windows 7"</span>,<span class="hljs-attr">"version"</span>:<span class="hljs-string">"6.1 SP1 (build 7601)"</span>},<span class="hljs-attr">"extra"</span>:{<span class="hljs-attr">"pageSize"</span>:<span class="hljs-number">4096</span>},<span class="hljs-attr">"ok"</span>:<span class="hljs-number">1</span>}
</code></pre>
<p>  我们只需通过编写<code>res.system.currentTime</code>来访问服务器时间。</p>
<p>  这里的连接是匿名的，只有当<code>mongod</code>实例在同一台计算机上运行时，它才会工作。可以通过<code>TMongoClient.OpenAuth()</code>方法进行安全的远程连接，包括用户身份验证：支持最新的<code>SCRAM-SHA-1</code>挑战响应机制（MongoDB 3.x以后支持），或废弃的<code>MONGODB-CR</code>（用于旧版本）。</p>
<pre><code class="lang-pascal hljs">    ...
  Client := TMongoClient.Create(<span class="hljs-string">'localhost'</span>,<span class="hljs-number">27017</span>);
  <span class="hljs-keyword">try</span>
    DB := Client.OpenAuth(<span class="hljs-string">'mydb'</span>,<span class="hljs-string">'mongouser'</span>,<span class="hljs-string">'mongopwd'</span>);
    ...
</code></pre>
<p>  出于安全原因，MongoDB服务器不要在没有经过身份验证的情况下允许远程访问，正如http://docs.mongodb.org/manual/admination/securit-access-control所述。</p>
<p>  <code>TMongoDatabase.CreateUser()</code>、<code>createuserforisdatabase()</code>和<code>DropUser()</code>方法允许轻松管理来自应用程序的凭据。</p>
<h3 id="toc_3">9.1.2. 向集合添加文档<a class="vnote-anchor" href="#toc_3" data-anchor-icon="#"></a></h3>
<p>  现在我们将解释如何向给定集合添加文档。</p>
<p>  我们假设有一个<code>DB: TMongoDatabase</code>实例可用，我们将使用<code>TDocVariant</code>实例创建文档，该实例将通过后期绑定填充和<code>doc.Clear</code>伪方法清除之前的属性值：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> Coll: TMongoCollection;
    doc: variant;
    i: integer;
<span class="hljs-keyword">begin</span>
  Coll := DB.CollectionOrCreate[COLL_NAME];
  TDocVariant.New(doc);
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span> <span class="hljs-keyword">to</span> <span class="hljs-number">10</span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">begin</span>
    doc.Clear;
    doc.<span class="hljs-keyword">Name</span> := <span class="hljs-string">'Name '</span>+IntToStr(i+<span class="hljs-number">1</span>);
    doc.Number := i;
    Coll.Save(doc);
    writeln(<span class="hljs-string">'Inserted with _id='</span>,doc._id);
  <span class="hljs-keyword">end</span>;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  由于<code>TDocVariant</code>后期绑定功能，代码非常容易理解和维护。</p>
<p>  这段代码将在控制台显示如下内容：</p>
<pre><code class="lang-sh hljs">Inserted with _id=5369029E4F901EE8114799D9
Inserted with _id=5369029E4F901EE8114799DA
Inserted with _id=5369029E4F901EE8114799DB
Inserted with _id=5369029E4F901EE8114799DC
Inserted with _id=5369029E4F901EE8114799DD
Inserted with _id=5369029E4F901EE8114799DE
Inserted with _id=5369029E4F901EE8114799DF
Inserted with _id=5369029E4F901EE8114799E0
Inserted with _id=5369029E4F901EE8114799E1
Inserted with _id=5369029E4F901EE8114799E2
</code></pre>
<p>  这意味着<code>Coll.Save()</code>方法非常聪明，能够理解所提供的文档没有<code>_id</code>字段，因此在将文档数据发送到MongoDB服务器之前，将在客户端计算一个。</p>
<p>  我们也可以这样写：</p>
<pre><code class="lang-pascal hljs">  <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span> <span class="hljs-keyword">to</span> <span class="hljs-number">10</span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">begin</span>
    doc.Clear;
    doc._id := ObjectID;
    doc.<span class="hljs-keyword">Name</span> := <span class="hljs-string">'Name '</span>+IntToStr(i+<span class="hljs-number">1</span>);
    doc.Number := i;
    Coll.Save(doc);
    writeln(<span class="hljs-string">'Inserted with _id='</span>,doc._id);
  <span class="hljs-keyword">end</span>;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  这将在调用<code>Coll.Save()</code>之前显式地计算文档标识符。</p>
<p>  在本例中，我们可以直接调用<code>Coll.Insert()</code>，这稍微快一些。</p>
<p>  没有强制您使用MongoDB ObjectID作为标识符，你可以使用任何值，如果你确信它是可用的，如你可以使用整数：</p>
<pre><code class="lang-pascal hljs">  <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span> <span class="hljs-keyword">to</span> <span class="hljs-number">10</span> <span class="hljs-keyword">do</span>
  <span class="hljs-keyword">begin</span>
    doc.Clear;
    doc._id := i;
    doc.<span class="hljs-keyword">Name</span> := <span class="hljs-string">'Name '</span>+IntToStr(i+<span class="hljs-number">1</span>);
    doc.Number := i;
    Coll.Insert(doc);
    writeln(<span class="hljs-string">'Inserted with _id='</span>,doc._id);
  <span class="hljs-keyword">end</span>;
<span class="hljs-keyword">end</span>;
</code></pre>
<p>  控制台现在显示：</p>
<pre><code class="lang-sh hljs">Inserted with _id=1
Inserted with _id=2
Inserted with _id=3
Inserted with _id=4
Inserted with _id=5
Inserted with _id=6
Inserted with _id=7
Inserted with _id=8
Inserted with _id=9
Inserted with _id=10
</code></pre>
<p>  mORMot ORM是以类似的方式计算可用的整数序列，TSQLRecord将按照要求使用这些<code>TSQLRecord.ID</code>主键属性。</p>
<p>  <code>TMongoCollection</code>类还可以写入文档列表，并将它们立即发送到MongoDB服务器：这种批量插入模式，接近一些SQL提供程序的数组绑定特性，并由我们的<code>SynDB.pas</code>类实现，可以将插入速度增加10倍，甚至在连接到本地实例时也是如此：想象一下，它在物理网络上可以节省多少时间！</p>
<p>  例如，你可以这样写:</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> docs: TVariantDynArray;
...
  SetLength(docs,COLL_COUNT);
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> COLL_COUNT-<span class="hljs-number">1</span> <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
    TDocVariant.New(docs[i]);
    docs[i]._id := ObjectID; <span class="hljs-comment">// compute new ObjectID on the client side</span>
    docs[i].<span class="hljs-keyword">Name</span> := <span class="hljs-string">'Name '</span>+IntToStr(i+<span class="hljs-number">1</span>);
    docs[i].FirstName := <span class="hljs-string">'FirstName '</span>+IntToStr(i+COLL_COUNT);
    docs[i].Number := i;
  <span class="hljs-keyword">end</span>;
  Coll.Insert(docs); <span class="hljs-comment">// insert all values at once</span>
...
</code></pre>
<p>  稍后您将说明由于这种批量插入而导致的速度增长的一些数字。</p>
<h3 id="toc_4">9.1.3. 检索文件<a class="vnote-anchor" href="#toc_4" data-anchor-icon="#"></a></h3>
<p>  您可以将文档检索为TDocVariant实例：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> doc: variant;
...
  doc := Coll.FindOne(<span class="hljs-number">5</span>);
  writeln(<span class="hljs-string">'Name: '</span>,doc.<span class="hljs-keyword">Name</span>);
  writeln(<span class="hljs-string">'Number: '</span>,doc.Number);
</code></pre>
<p>  将在控制台写入:</p>
<pre><code class="lang-sh hljs">Name: Name 6
Number: 5
</code></pre>
<p>  如果需要，您可以访问整个<code>Query</code>参数：</p>
<pre><code class="lang-pascal hljs">  doc := Coll.FindDoc(<span class="hljs-string">'{_id:?}'</span>,[<span class="hljs-number">5</span>]);
  doc := Coll.FindOne(<span class="hljs-number">5</span>); <span class="hljs-comment">// same as previous</span>
</code></pre>
<p>  这个<code>Query</code>过滤器类似于SQL中的WHERE子句，如果需要，您可以编写复杂的查询模式，参见http://docs.mongodb.org/manual/reference/method/db.collection.find。</p>
<p>  您可以将文档列表检索为TDocVariant的动态数组：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> docs: TVariantDynArray;
...
  Coll.FindDocs(docs);
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">0</span> <span class="hljs-keyword">to</span> high(docs) <span class="hljs-keyword">do</span>
    writeln(<span class="hljs-string">'Name: '</span>,docs[i].<span class="hljs-keyword">Name</span>,<span class="hljs-string">'  Number: '</span>,docs[i].Number);
</code></pre>
<p>  将输出:</p>
<pre><code class="lang-sh hljs">Name: Name 2  Number: 1
Name: Name 3  Number: 2
Name: Name 4  Number: 3
Name: Name 5  Number: 4
Name: Name 6  Number: 5
Name: Name 7  Number: 6
Name: Name 8  Number: 7
Name: Name 9  Number: 8
Name: Name 10  Number: 9
Name: Name 11  Number: 10
</code></pre>
<p>  在GUI应用中，可以使用<code>SynVirtualDataSet.pas</code>中定义的<code>TDocVariantArrayDataSet</code>填充VCL表格：</p>
<pre><code class="lang-pascal hljs"> ds1.DataSet.Free; <span class="hljs-comment">// release previous TDataSet</span>
 ds1.DataSet := ToDataSet(self,FindDocs(<span class="hljs-string">'{name:?,age:{$gt:?}}'</span>,[<span class="hljs-string">'John'</span>,<span class="hljs-number">21</span>],null));
</code></pre>
<p>  这个重载的<code>FindDocs()</code>方法接受一个JSON查询过滤器和参数（遵循MongoDB语法），以及一个投影映射（<code>null</code>以检索所有属性）。它返回一个<code>TVariantDynArray</code>结果，该结果使用重载的<code>ToDataSet()</code>函数映射到优化的只读<code>TDataSet</code>。所以在我们的例子中，DB表格中填满了所有名为'<code>John</code>'、年龄大于21岁的人。</p>
<p>  如果你想直接以JSON格式检索文档，我们可以这样写：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-keyword">var</span> json: RawUTF8;
...
  json := Coll.FindJSON(null,null);
  writeln(json);
...
</code></pre>
<p>  这将把以下内容输出到控制台：</p>
<pre><code class="lang-json hljs">[{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">1</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 2"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">1</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">2</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 3"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">2</span>},{<span class="hljs-attr">"_id
"</span>:<span class="hljs-number">3</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 4"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">3</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">4</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 5"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">4</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">5</span>,<span class="hljs-attr">"N
ame"</span>:<span class="hljs-string">"Name 6"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">5</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">6</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 7"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">6</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">7</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"
Name 8"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">7</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">8</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 9"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">8</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">9</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 1
0"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">9</span>},{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">10</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 11"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">10</span>}]
</code></pre>
<p>  可以注意到<code>FindJSON()</code>有两个属性，一个是查询过滤器，另一个是投影映射（类似于<code>SELECT col1,col2</code>中的列名）。</p>
<p>  所以我们可以这样写：</p>
<pre><code class="lang-pascal hljs">  json := Coll.FindJSON(<span class="hljs-string">'{_id:?}'</span>,[<span class="hljs-number">5</span>]);
  writeln(json);
</code></pre>
<p>  将输出：</p>
<pre><code class="lang-json hljs">[{<span class="hljs-attr">"_id"</span>:<span class="hljs-number">5</span>,<span class="hljs-attr">"Name"</span>:<span class="hljs-string">"Name 6"</span>,<span class="hljs-attr">"Number"</span>:<span class="hljs-number">5</span>}]
</code></pre>
<p>  这里我们使用重载的<code>FindJSON()</code>方法，它接受MongoDB扩展语法（这里字段名未加引号）和参数作为变量。</p>
<p>  我们可以指定一个投影：</p>
<pre><code class="lang-pascal hljs">  json := Coll.FindJSON(<span class="hljs-string">'{_id:?}'</span>,[<span class="hljs-number">5</span>],<span class="hljs-string">'{Name:1}'</span>);
  writeln(json);
</code></pre>
<p>  它只返回“Name”和“_id”字段（因为根据MongoDB约定，<code>_id</code>总是返回）：</p>
<pre><code class="lang-sh hljs">[{<span class="hljs-string">"_id"</span>:5,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"Name 6"</span>}]
</code></pre>
<p>  若仅返回“Name”字段，可以使用JSON扩展语法将<code>'_id:0,Name:1'</code>指定为投影参数。</p>
<pre><code class="lang-sh hljs">[{<span class="hljs-string">"Name"</span>:<span class="hljs-string">"Name 6"</span>}]
</code></pre>
<p>  还有其他方法可以检索数据，也可以直接作为BSON二进制数据。它们将拥有最佳速度，如与ORM结合使用，但是对于大多数最终用户代码，使用<code>TDocVariant</code>更安全易于维护。</p>
<h4 id="toc_5">9.1.3.1. 更新或删除文档<a class="vnote-anchor" href="#toc_5" data-anchor-icon="#"></a></h4>
<p>  <code>TMongoCollection</code>类有一些用于修改现有文档的方法。</p>
<p>  首先，<code>Save()</code>方法可用于更个首先检索到的文档:</p>
<pre><code class="lang-pascal hljs">  doc := Coll.FindOne(<span class="hljs-number">5</span>);
  doc.<span class="hljs-keyword">Name</span> := <span class="hljs-string">'New!'</span>;
  Coll.Save(doc);
  writeln(<span class="hljs-string">'Name: '</span>,Coll.FindOne(<span class="hljs-number">5</span>).<span class="hljs-keyword">Name</span>);
</code></pre>
<p>  将输出：</p>
<pre><code class="lang-sh hljs">Name: New!
</code></pre>
<p>  注意，这里我们使用了一个整数值（5）作为关键字，但如果需要，我们可以使用一个ObjectID。</p>
<p>  <code>Coll.Save()</code>方法可以更改为<code>Coll.Update()</code>，更新文档内容时需要显式指定<code>Query</code>操作符：</p>
<pre><code class="lang-pascal hljs">  doc := Coll.FindOne(<span class="hljs-number">5</span>);
  doc.<span class="hljs-keyword">Name</span> := <span class="hljs-string">'New!'</span>;
  Coll.Update(BSONVariant([<span class="hljs-string">'_id'</span>,<span class="hljs-number">5</span>]),doc);
  writeln(<span class="hljs-string">'Name: '</span>,Coll.FindOne(<span class="hljs-number">5</span>).<span class="hljs-keyword">Name</span>);
</code></pre>
<p>  注意，根据MongoDB的设计，对Update()的任何调用都将替换整个文档。</p>
<p>  例如，如果你写成：</p>
<pre><code class="lang-pascal hljs">  writeln(<span class="hljs-string">'Before: '</span>,Coll.FindOne(<span class="hljs-number">3</span>));
  Coll.Update(<span class="hljs-string">'{_id:?}'</span>,[<span class="hljs-number">3</span>],<span class="hljs-string">'{Name:?}'</span>,[<span class="hljs-string">'New Name!'</span>]);
  writeln(<span class="hljs-string">'After:  '</span>,Coll.FindOne(<span class="hljs-number">3</span>));
</code></pre>
<p>  那么<code>Number</code>字段将消失！</p>
<pre><code class="lang-sh hljs">Before: {<span class="hljs-string">"_id"</span>:3,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"Name 4"</span>,<span class="hljs-string">"Number"</span>:3}
After:  {<span class="hljs-string">"_id"</span>:3,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"New Name!"</span>}
</code></pre>
<p>  如果只需要更新某些字段，则必须使用<code>$set</code>修饰符：</p>
<pre><code class="lang-pascal hljs">  writeln(<span class="hljs-string">'Before: '</span>,Coll.FindOne(<span class="hljs-number">4</span>));
  Coll.Update(<span class="hljs-string">'{_id:?}'</span>,[<span class="hljs-number">4</span>],<span class="hljs-string">'{$set:{Name:?}}'</span>,[<span class="hljs-string">'New Name!'</span>]);
  writeln(<span class="hljs-string">'After:  '</span>,Coll.FindOne(<span class="hljs-number">4</span>));
</code></pre>
<p>  这将在控制台输出：</p>
<pre><code class="lang-sh hljs">Before: {<span class="hljs-string">"_id"</span>:4,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"Name 5"</span>,<span class="hljs-string">"Number"</span>:4}
After:  {<span class="hljs-string">"_id"</span>:4,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"New Name!"</span>,<span class="hljs-string">"Number"</span>:4}
</code></pre>
<p>  现在<code>Number</code>字段保持不变。</p>
<p>  您还可以使用<code>Coll.UpdateOne()</code>方法，该方法将更新提供的字段，并保持未指定字段不变：</p>
<pre><code class="lang-pascal hljs">  writeln(<span class="hljs-string">'Before: '</span>,Coll.FindOne(<span class="hljs-number">2</span>));
  Coll.UpdateOne(<span class="hljs-number">2</span>,_Obj([<span class="hljs-string">'Name'</span>,<span class="hljs-string">'NEW'</span>]));
  writeln(<span class="hljs-string">'After:  '</span>,Coll.FindOne(<span class="hljs-number">2</span>));
</code></pre>
<p>  将输出：</p>
<pre><code class="lang-sh hljs">Before: {<span class="hljs-string">"_id"</span>:2,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"Name 3"</span>,<span class="hljs-string">"Number"</span>:2}
After:  {<span class="hljs-string">"_id"</span>:2,<span class="hljs-string">"Name"</span>:<span class="hljs-string">"NEW"</span>,<span class="hljs-string">"Number"</span>:2}
</code></pre>
<p>  您可以参考`SynMongoDB.pas单元的文档，找到所有可用的函数、类和方法来使用MongoDB。</p>
<p>  还有一些非常强大的可用特性，包括聚合（MongoDB 2.2开始提供），是标准Map/Reduce模式的一个很好的替代方案。</p>
<p>  参考http://docs.mongodb.org/manual/reference/command/aggregate。</p>
<h3 id="toc_6">9.1.4. 写回执和性能<a class="vnote-anchor" href="#toc_6" data-anchor-icon="#"></a></h3>
<p>  您可以查看一下<code>MongoDBTests.dpr</code>示例，位于源代码存储库<code>SQLite3\Samples\24 - MongoDB</code>子文件夹，及<code>TTestDirect</code>类，以找到一些性能信息。</p>
<p>  实际上，这个<code>TTestDirect</code>被继承了两次，以便以不同的写回执运行相同的测试：</p>
<div class="mermaid-diagram"><svg id="mermaid-diagram-2" xmlns="http://www.w3.org/2000/svg" height="100%" viewBox="0 0 586 198" style="max-width:586px;"><style type="text/css" title="mermaid-svg-internal-css">/*  */
#mermaid-diagram-2 .node&gt;rect { ; }
#mermaid-diagram-2 .node text  { fill:#000; stroke:none; font-weight:300; font-family:"Helvetica Neue",Helvetica,Arial,sans-serf; font-size:14px; }
#mermaid-diagram-2 .edgeLabel text  { fill:#000; stroke:none; font-weight:300; font-family:"Helvetica Neue",Helvetica,Arial,sans-serf; font-size:14px; }
#mermaid-diagram-2 .cluster rect  { rx:4px; fill: rgb(255, 255, 222); rx: 4px; stroke: rgb(170, 170, 51); stroke-width: 1px; }
#mermaid-diagram-2 .cyan&gt;rect, .cyan&gt;polygon, .cyan&gt;circle, .cyan&gt;ellipse { fill:#9ff;  stroke:#fff; }
/*  */
</style><g><g class="output"><g class="clusters"></g><g class="edgePaths"><g class="edgePath" style="opacity: 1;"><path class="path" d="M133,64L133,89L228,120.00694444444444" marker-end="url(#arrowhead76)" style="fill:none"></path><defs><marker id="arrowhead76" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath" style="opacity: 1;"><path class="path" d="M421,64L421,89L326,120.00694444444444" marker-end="url(#arrowhead77)" style="fill:none"></path><defs><marker id="arrowhead77" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g></g><g class="edgeLabels"><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel"></span></div></foreignObject></g></g><g class="edgeLabel" transform="" style="opacity: 1;"><g transform="translate(0,0)" class="label"><foreignObject width="0" height="0"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; white-space: nowrap;"><span class="edgeLabel"></span></div></foreignObject></g></g></g><g class="nodes"><g class="node cyan" id="A" transform="translate(133,42)" style="opacity: 1;"><rect rx="0" ry="0" x="-113" y="-22" width="226" height="44"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-103,-12)"><foreignObject width="206" height="24"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">TTestDirectWithAcknowledge</div></foreignObject></g></g></g><g class="node cyan" id="C" transform="translate(277,136)" style="opacity: 1;"><rect rx="0" ry="0" x="-49" y="-22" width="98" height="44"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-39,-12)"><foreignObject width="78" height="24"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">TTestDirect</div></foreignObject></g></g></g><g class="node cyan" id="B" transform="translate(421,42)" style="opacity: 1;"><rect rx="0" ry="0" x="-125" y="-22" width="250" height="44"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-115,-12)"><foreignObject width="230" height="24"><div style="display: inline-block; white-space: nowrap;" xmlns="http://www.w3.org/1999/xhtml">TTestDirectWithoutAcknowledge</div></foreignObject></g></g></g></g></g></g></svg></div>
<p>  两个类之间的差异主要发生在客户端初始化：</p>
<pre><code class="lang-pascal hljs"><span class="hljs-function"><span class="hljs-keyword">procedure</span> <span class="hljs-title">TTestDirect</span>.<span class="hljs-title">ConnectToLocalServer</span>;</span>
...
  fClient := TMongoClient.Create(<span class="hljs-string">'localhost'</span>,<span class="hljs-number">27017</span>);
  <span class="hljs-keyword">if</span> ClassType=TTestDirectWithAcknowledge <span class="hljs-keyword">then</span>
    fClient.WriteConcern := wcAcknowledged <span class="hljs-keyword">else</span>
  <span class="hljs-keyword">if</span> ClassType=TTestDirectWithoutAcknowledge <span class="hljs-keyword">then</span>
    fClient.WriteConcern := wcUnacknowledged;
...
</code></pre>
<p>  <code>wcAcknowledged</code>是默认的安全模式：MongoDB服务器需要确认收到写操作。已确认的写回执允许客户端捕获网络、重复主键和其他错误。但是它增加了从客户机到服务器的额外往返，并且在返回错误状态之前等待命令完成，因此它将减慢写进程。</p>
<p>  在<code>wcUnacknowledged</code>的情况下，MongoDB不确认收到写操作，不确认就类似于忽略错误；但驱动程序试图在可能的情况下接收和处理网络错误，驱动程序检测网络错误的能力取决于系统的网络配置。</p>
<p>  两者之间的速度差异值得一提，正如回归测试状态所述，本地MongoDB实例上运行：</p>
<pre><code class="lang-sh hljs">1. Direct access

 1.1. Direct with acknowledge:
  - Connect to <span class="hljs-built_in">local</span> server: 6 assertions passed  4.72ms
  - Drop and prepare collection: 8 assertions passed  9.38ms
  - Fill collection: 15,003 assertions passed  558.79ms
     5000 rows inserted <span class="hljs-keyword">in</span> 548.83ms i.e. 9110/s, aver. 109us, 3.1 MB/s
  - Drop collection: no assertion  856us
  - Fill collection bulk: 2 assertions passed  74.59ms
     5000 rows inserted <span class="hljs-keyword">in</span> 64.76ms i.e. 77204/s, aver. 12us, 7.2 MB/s
  - Read collection: 30,003 assertions passed  2.75s
     5000 rows <span class="hljs-built_in">read</span> at once <span class="hljs-keyword">in</span> 9.66ms i.e. 517330/s, aver. 1us, 39.8 MB/s
  - Update collection: 7,503 assertions passed  784.26ms
     5000 rows updated <span class="hljs-keyword">in</span> 435.30ms i.e. 11486/s, aver. 87us, 3.7 MB/s
  - Delete some items: 4,002 assertions passed  370.57ms
     1000 rows deleted <span class="hljs-keyword">in</span> 96.76ms i.e. 10334/s, aver. 96us, 2.2 MB/s
  Total failed: 0 / 56,527  - Direct with acknowledge PASSED  4.56s

 1.2. Direct without acknowledge:
  - Connect to <span class="hljs-built_in">local</span> server: 6 assertions passed  1.30ms
  - Drop and prepare collection: 8 assertions passed  8.59ms
  - Fill collection: 15,003 assertions passed  192.59ms
     5000 rows inserted <span class="hljs-keyword">in</span> 168.50ms i.e. 29673/s, aver. 33us, 4.4 MB/s
  - Drop collection: no assertion  845us
  - Fill collection bulk: 2 assertions passed  68.54ms
     5000 rows inserted <span class="hljs-keyword">in</span> 58.67ms i.e. 85215/s, aver. 11us, 7.9 MB/s
  - Read collection: 30,003 assertions passed  2.75s
     5000 rows <span class="hljs-built_in">read</span> at once <span class="hljs-keyword">in</span> 9.99ms i.e. 500150/s, aver. 1us, 38.5 MB/s
  - Update collection: 7,503 assertions passed  446.48ms
     5000 rows updated <span class="hljs-keyword">in</span> 96.27ms i.e. 51933/s, aver. 19us, 7.7 MB/s
  - Delete some items: 4,002 assertions passed  297.26ms
     1000 rows deleted <span class="hljs-keyword">in</span> 19.16ms i.e. 52186/s, aver. 19us, 2.8 MB/s
  Total failed: 0 / 56,527  - Direct without acknowledge PASSED  3.77s
</code></pre>
<p>  如您所见，读取速度不受写回执设置的影响。</p>
<p>  但是，当写命令都不需要回执时，数据写入速度可能会快好几倍。</p>
<p>  由于没有错误处理，<code>wcUnacknowledged</code>不能用于生产。您可以将其用于复制或数据整合，如以尽可能快的速度为数据库提供大量现有数据。</p>
<h2 id="toc_7">9.2. MongoDB + ORM = ODM<a class="vnote-anchor" href="#toc_7" data-anchor-icon="#"></a></h2>
<p>  <code>mORMotMongoDB.pas</code>单元能够在远程MongoDB服务器上持久化任何<code>TSQLRecord</code>类。</p>
<p>  因此，我们的ORM可以作为<a href="">NoSQL和对象文档映射（ODM）</a>框架使用，几乎不需要更改代码。任何MongoDB数据库都可以通过RESTful命令访问，使用JSON而不是HTTP。</p>
<p>  这种集成得益于框架的其他部分（如我们的utf-8专用处理，这也是BSON原生编码），所以你可以很容易地在相同的代码中混合使用SQL和NoSQL数据库，并仍然能够根据需要在代码中调整任何SQL或MongoDB请求。</p>
<p>  从客户端的角度来看，ORM或ODM没有区别：你可以使用一个SQL引擎用于ODM，通过存储无共享体系结构（或分区），甚至反规范化地将NoSQL数据库用于一个常规的ORM（即使这样可能损失NoSQL的优势）。</p>
<h3 id="toc_8">9.2.1. 定义TSQLRecord类<a class="vnote-anchor" href="#toc_8" data-anchor-icon="#"></a></h3>
<p>  在数据库模型中，我们像往常一样定义了一个<code>TSQLRecord</code>类：</p>
<pre><code class="lang-pascal hljs">  <span class="hljs-title">TSQLORM</span> = <span class="hljs-keyword">class</span>(TSQLRecord)
  <span class="hljs-keyword">private</span>
    fAge: integer;
    fName: RawUTF8;
    fDate: TDateTime;
    fValue: variant;
    fInts: TIntegerDynArray;
    fCreateTime: TCreateTime;
    fData: TSQLRawBlob;
  <span class="hljs-keyword">published</span>
    <span class="hljs-keyword">property</span> <span class="hljs-keyword">Name</span>: RawUTF8 <span class="hljs-keyword">read</span> fName <span class="hljs-keyword">write</span> fName <span class="hljs-keyword">stored</span> AS_UNIQUE;
    <span class="hljs-keyword">property</span> Age: integer <span class="hljs-keyword">read</span> fAge <span class="hljs-keyword">write</span> fAge;
    <span class="hljs-keyword">property</span> Date: TDateTime <span class="hljs-keyword">read</span> fDate <span class="hljs-keyword">write</span> fDate;
    <span class="hljs-keyword">property</span> Value: variant <span class="hljs-keyword">read</span> fValue <span class="hljs-keyword">write</span> fValue;
    <span class="hljs-keyword">property</span> Ints: TIntegerDynArray <span class="hljs-keyword">index</span> <span class="hljs-number">1</span> <span class="hljs-keyword">read</span> fInts <span class="hljs-keyword">write</span> fInts;
    <span class="hljs-keyword">property</span> Data: TSQLRawBlob <span class="hljs-keyword">read</span> fData <span class="hljs-keyword">write</span> fData;
    <span class="hljs-keyword">property</span> CreateTime: TCreateTime <span class="hljs-keyword">read</span> fCreateTime <span class="hljs-keyword">write</span> fCreateTime;
  <span class="hljs-keyword">end</span>;
</code></pre>
<p>  注意，我们没有为RawUTF8属性定义任何<code>index ...</code>值，因为我们使用外部SQL数据库，而MongoDB不需要限制文本字段长度（据我所知，唯一原生地支持这种属性而不影响性能的SQL引擎是SQlite3和PostgreSQL）。</p>
<p>  属性值将存储在本地MongoDB中，相对于SQL类型，我们的<code>SynDB*</code>单元支持的类型得更多：</p>
<table>
<thead>
<tr>
<th>Delphi</th>
<th>MongoDB</th>
<th>备注</th>
</tr>
</thead>
<tbody>
<tr>
<td>byte</td>
<td>Int32</td>
<td></td>
</tr>
<tr>
<td>Word</td>
<td>Int32</td>
<td></td>
</tr>
<tr>
<td>Integer</td>
<td>Int32</td>
<td></td>
</tr>
<tr>
<td>Cardinal</td>
<td>N/A</td>
<td>应该用Int64代替</td>
</tr>
<tr>
<td>Int64</td>
<td>Int64</td>
<td></td>
</tr>
<tr>
<td>boolean</td>
<td>boolean</td>
<td>MongoDB支持boolean类型</td>
</tr>
<tr>
<td>enumeration</td>
<td>Int32</td>
<td>存储枚举项的序号值（第一个元素从0开始）</td>
</tr>
<tr>
<td>set</td>
<td>Int64</td>
<td>每个位对应一个枚举项（因此最多可以存储64个元素）</td>
</tr>
<tr>
<td>single</td>
<td>double</td>
<td></td>
</tr>
<tr>
<td>double</td>
<td>double</td>
<td></td>
</tr>
<tr>
<td>extended</td>
<td>double</td>
<td>存储为双精度类型（有精度损失）</td>
</tr>
<tr>
<td>currency</td>
<td>double</td>
<td>存储为双精度类型 （MongoDB没有BSD类型）</td>
</tr>
<tr>
<td>RawUTF8</td>
<td>UTF-8</td>
<td>ORM中存储文本内容的首选字段类型</td>
</tr>
<tr>
<td>WinAnsiString</td>
<td>UTF-8</td>
<td>Delphi的WinAnsi字符集（1252代码页）</td>
</tr>
<tr>
<td>RawUnicode</td>
<td>UTF-8</td>
<td>Delphi的UCS2字符集，作为AnsiString</td>
</tr>
<tr>
<td>WideString</td>
<td>UTF-8</td>
<td>UCS2字符集，作为COM BSTR类型（Delphi所有版本的Unicode）</td>
</tr>
<tr>
<td>SynUnicode</td>
<td>UTF-8</td>
<td>Delphi 2009之前为WideString，之后为UnicodeString</td>
</tr>
<tr>
<td>string</td>
<td>UTF-8</td>
<td>Delphi 2009之前未使用（否则在转换过程中可能会丢失数据），在所有情况下，RawUTF8都是首选</td>
</tr>
<tr>
<td>TDateTime<br>TDateTimeMS</td>
<td>datetime</td>
<td>ISO 8601日期时间编码</td>
</tr>
<tr>
<td>TTimeLog</td>
<td>Int64</td>
<td>专用的快速Int64日期时间</td>
</tr>
<tr>
<td>TModTime</td>
<td>Int64</td>
<td>修改记录时将存储为服务器日期时间（专用快速Int64）</td>
</tr>
<tr>
<td>TCreateTime</td>
<td>Int64</td>
<td>创建记录时将存储为服务器日期时间（专用快速Int64）</td>
</tr>
<tr>
<td>TUnixTime</td>
<td>Datetime</td>
<td>Unix时间以来的秒数</td>
</tr>
<tr>
<td>TSQLRecord</td>
<td>Int32</td>
<td>指向另一条记录的32位<code>RowID</code>，字段值包含的是<code>pointer(RowID)</code>，而不是有效的对象实例，记录内容必须通过<code>PtrInt(Field)</code>类型转换或<code>Field.ID</code>进行后期绑定ID来检索；或使用<code>CreateJoined()</code>，在Win64上是64位的</td>
</tr>
<tr>
<td>TID</td>
<td>int32/int64</td>
<td>指向另一条记录的RowID，这种属性是64位兼容的，因此最大可以处理到9,223,372,03,864,775,808</td>
</tr>
<tr>
<td>TSQLRecordMany</td>
<td>Nothing</td>
<td>数据存储在单独的数据透视表中；对于MongoDB，您应该更好地使用数据分片和嵌入式子文档</td>
</tr>
<tr>
<td>TRecordReference<br>TRecordReferenceToBeDeleted</td>
<td>Int32/int64</td>
<td>将<code>ID</code>和<code>TSQLRecord</code>类型存储在一个类似RecordRef的值中，在删除记录时会正确同步</td>
</tr>
<tr>
<td>TPersistent</td>
<td>object</td>
<td>BSON对象（通过ObjectToJSON）</td>
</tr>
<tr>
<td>TCollection</td>
<td>array</td>
<td>BSON对象数组（通过ObjectToJSON）</td>
</tr>
<tr>
<td>TObjectList</td>
<td>array</td>
<td>BSON对象数组（通过ObjectToJSON），参见<code>TJSONSerializer.RegisterClassForJSON</code></td>
</tr>
<tr>
<td>TStrings</td>
<td>array</td>
<td>BSON字符串数组（通过ObjectToJSON）</td>
</tr>
<tr>
<td>TRawUTF8List</td>
<td>array</td>
<td>BSON字符串数组（通过ObjectToJSON）</td>
</tr>
<tr>
<td>any <code>TObject</code></td>
<td>object</td>
<td>参见<code>TJSONSerializer.RegisterCustomSerializer</code></td>
</tr>
<tr>
<td>TSQLRawBlob</td>
<td>binary</td>
<td>RawByteString的别名，这些属性在默认情况下不会被检索：您需要使用<code>RetrieveBlobFields()</code>或设置<code>ForceBlobTransfert / ForceBlobTransertTable[]</code>属性</td>
</tr>
<tr>
<td>TByteDynArray</td>
<td>binary</td>
<td>将BLOB属性作为BSON二进制文件存储在文档中，将来TSQLRawBlob可能被限制为GridFS外部内容</td>
</tr>
<tr>
<td><em>dynamic arrays</em></td>
<td>array<br>binary</td>
<td>如果动态数组可以保存为真正的JSON，则存储为BSON数组，否则按TDynArray.SaveTo存储为BSON二进制格式</td>
</tr>
<tr>
<td>variant</td>
<td>value<br>array</td>
<td></td>
</tr>
<tr>
<td>object</td>
<td>BSON数值、文本、日期、对象或数组，取决于TDocVariant自定义变体类型或TBSONVariant存储值（如存储MongoDB原生类型，如ObjectID或Decimal128）</td>
<td></td>
</tr>
<tr>
<td>record</td>
<td>binary<br>object</td>
<td>通过覆盖生成BSON的TSQLRecord.InternalRegisterCustomProperties，以生成真正的JSON</td>
</tr>
</tbody>
</table>
<p>  您可以与MongoDB和其他存储方式（如外部SQL数据库）共享相同的<code>TSQLRecord</code>定义，忽略不支持的信息（如索引属性）。</p>
<p>  注意<code>TSQLRecord</code>、<code>TID</code>和<code>TRecordReference*</code>发布属性会在对应字段自动创建索引，<code>TSQLRecord</code>和<code>TRecordReference</code>属性会跟踪<code>ON DELETE SET DEFAULT</code>操作，<code>TRecordReferenceToBeDeleted</code>会跟踪<code>ON DELETE CASCADE</code> 操作，但<code>TID</code>不会，因为我们不知道跟踪哪个表。</p>
<h3 id="toc_9">9.2.2. 注册TSQLRecord类<a class="vnote-anchor" href="#toc_9" data-anchor-icon="#"></a></h3>
<p>  在服务端（客户端也不会有任何区别），您定义了一个<code>TMongoDBClient</code>，并通过调用<code>StaticMongoDBRegister()</code>将其分配给一个给定的<code>TSQLRecord类</code>：</p>
<pre><code class="lang-pascal hljs">  MongoClient := TMongoClient.Create(<span class="hljs-string">'localhost'</span>,<span class="hljs-number">27017</span>);
  DB := MongoClient.Database[<span class="hljs-string">'dbname'</span>];
  Model := TSQLModel.Create([TSQLORM]);
  Client := TSQLRestClientDB.Create(Model,<span class="hljs-keyword">nil</span>,<span class="hljs-string">':memory:'</span>,TSQLRestServerDB);
  <span class="hljs-keyword">if</span> StaticMongoDBRegister(TSQLORM,fClient.Server,fDB,<span class="hljs-string">'collectionname'</span>)=<span class="hljs-keyword">nil</span> <span class="hljs-keyword">then</span>
    <span class="hljs-keyword">raise</span> Exception.Create(<span class="hljs-string">'Error'</span>);
</code></pre>
<p>  这就是所有！</p>
<p>  如果mORMot服务端的所有表都应该驻留在MongoDB服务器上，那么也可以调用<code>StaticMongoDBRegisterAll()</code>函数：</p>
<pre><code class="lang-pascal hljs">StaticMongoDBRegisterAll(aServer,aMongoClient.Open(<span class="hljs-string">'colllectionname'</span>));
</code></pre>
<p>  如果您想调用<code>TSQLRecord.InitializeTable</code>方法创建void表（如创建TSQLAuthGroup和TSQLAuthUser默认内容），可以执行以下命令：</p>
<pre><code class="lang-pascal hljs">Client.Server.InitializeTables(INITIALIZETABLE_NOINDEX);
</code></pre>
<p>  之后您可以像往常一样执行任何ORM命令：</p>
<pre><code class="lang-pascal hljs">  writeln(Client.TableRowCount(TSQLORM)=<span class="hljs-number">0</span>);
</code></pre>
<p>  与外部数据库一样，您可以指定对象和MongoDB集合之间的字段名映射。</p>
<p>  默认情况下是<code>TSQLRecord.ID</code>属性映射到MongoDB的<code>_id</code>字段，ORM将用一个整数值序列填充这个<code>_id</code>字段，就像任何TSQLRecord表一样。</p>
<p>  你可以指定你自己的映射，如：</p>
<pre><code class="lang-pascal hljs"> aModel.Props[aClass].ExternalDB.MapField(..)
</code></pre>
<p>  由于字段名存储在文档本身中，因此最好对MongoDB集合使用更短的命名。在处理大量文档时，它可以节省一些存储空间。</p>
<p>  一旦将TSQLRecord映射到一个MongoDB集合，您总是可以在稍后直接访问相应的<code>TMongoCollection</code>实例，只需使用一个简单的类型转换：</p>
<pre><code class="lang-pascal hljs"> (aServer.StaticDataServer[aClass] <span class="hljs-keyword">as</span> TSQLRestStorageMongoDB).Collection
</code></pre>
<p>  这可能允许任何特定任务，包括任何优化的查询或处理。</p>
<h3 id="toc_10">9.2.3. ORM / ODM CRUD方法<a class="vnote-anchor" href="#toc_10" data-anchor-icon="#"></a></h3>
<p>  您可以像往常一样，使用ORM的标准CRUD方法添加文档：</p>
<pre><code class="lang-pascal hljs">  R := TSQLORM.Create;
  <span class="hljs-keyword">try</span>
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span> <span class="hljs-keyword">to</span> COLL_COUNT <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
      R.<span class="hljs-keyword">Name</span> := <span class="hljs-string">'Name '</span>+Int32ToUTF8(i);
      R.Age := i;
      R.Date := <span class="hljs-number">1.0</span>*(<span class="hljs-number">30000</span>+i);
      R.Value := _ObjFast([<span class="hljs-string">'num'</span>,i]);
      R.Ints := <span class="hljs-keyword">nil</span>;
      R.DynArray(<span class="hljs-number">1</span>).Add(i);
      assert(Client.Add(R,True)=i);
    <span class="hljs-keyword">end</span>;
  <span class="hljs-keyword">finally</span>
    R.Free;
  <span class="hljs-keyword">end</span>;
</code></pre>
<p>  正如我们已经看到的，框架能够处理任何类型的属性，包括动态数组或变体等复杂类型。</p>
<p>  在上面的代码中，一个<code>TDocVariant</code>文档存储在<code>R.Value</code>中，并通过索引1和<code>TSQLRecord.DynArray()</code>方法访问动态整数数组。</p>
<p>  常用的<code>Retrieve</code> / <code>Delete</code> / <code>Update</code>方法也可用有：</p>
<pre><code class="lang-pascal hljs">  R := TSQLORM.Create;
  <span class="hljs-keyword">try</span>
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span> <span class="hljs-keyword">to</span> COLL_COUNT <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
      Check(Client.Retrieve(i,R));
      <span class="hljs-comment">// here R instance contains all values of one document, excluding BLOBs</span>
    <span class="hljs-keyword">end</span>;
  <span class="hljs-keyword">finally</span>
    R.Free;
  <span class="hljs-keyword">end</span>;
</code></pre>
<p>  你可以定义一个WHERE子句，就好像后台是一个普通的SQL数据库：</p>
<pre><code class="lang-pascal hljs">    R := TSQLORM.CreateAndFillPrepare(Client,<span class="hljs-string">'ID=?'</span>,[i]);
    <span class="hljs-keyword">try</span>
    ...
</code></pre>
<h3 id="toc_11">9.2.4. ODM复杂查询<a class="vnote-anchor" href="#toc_11" data-anchor-icon="#"></a></h3>
<p>  要执行查询并检索多个文档的内容，可以使用常规的<code>CreateAndFillPrepare</code>或<code>FillPrepare</code>方法：</p>
<pre><code class="lang-pascal hljs">  R := TSQLORM.CreateAndFillPrepare(Client,WHERE_CLAUSE,[WHERE_PARAMETERS]);
  <span class="hljs-keyword">try</span>
    n := <span class="hljs-number">0</span>;
    <span class="hljs-keyword">while</span> R.FillOne <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
      <span class="hljs-comment">// here R instance contains all values of one document, excluding BLOBs</span>
      inc(n);
    <span class="hljs-keyword">end</span>;
    assert(n=COLL_COUNT);
  <span class="hljs-keyword">finally</span>
    R.Free;
  <span class="hljs-keyword">end</span>;
</code></pre>
<p>  还可以为<code>CreateAndFillPrepare</code>或<code>FillPrepare</code>方法定义WHERE子句，WHERE子句可以包含几个与<code>AND</code> / <code>OR</code>连接的表达式。</p>
<p>  下面这些表达都可以用:</p>
<ul>
<li>简单比较器<code>= &lt; &lt;= &lt;&gt; &gt; &gt;=</code>，</li>
<li><code>IN (....)</code>子句，</li>
<li><code>IS NULL</code> / <code>IS NOT NULL</code> 判断，</li>
<li><code>LIKE</code>操作,</li>
<li>甚至各种<code>...DynArrayContains()</code>特定函数。</li>
</ul>
<p>  mORMot ODM将把这个类似SQL的语句转换为优化的MongoDB查询表达式，例如，使用<code>LIKE</code>操作符的正则表达式。</p>
<p>  <code>LIMIT</code>、<code>OFFSET</code>和<code>ORDER BY</code>子句也将按照要求进行处理。应该特别注意文本值上的<code>ORDER BY</code>：按照设计，MongoDB总是以区分大小写的方式对文本进行排序，这不是我们所期望的，所以我们的ODM将从MongoDB服务器检索到这些内容后在客户端对这些内容进行排序。对于数值字段，MongoDB的排序特性将在服务器端进行处理。</p>
<p>  <code>COUNT(*)</code>函数也将被转换成适当的MongoDB API调用，以便这样的操作将尽可能地节省成本。<code>DISTINCT() MAX() MIN() SUM() AVG()</code>函数和<code>GROUP BY</code>子句也将动态转换为优化的MongoDB聚合操作，您甚至可以为列设置别名（如<code>max(RowID) as first</code>），并对整数值执行简单的加减操作。</p>
<p>  下面是一些典型的WHERE子句，以及ODM生成的相应MongoDB查询文档：</p>
<table>
<thead>
<tr>
<th>WHERE子句</th>
<th>MongoDB查询</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>'Name=?',['Name 43']</code></td>
<td>{<code>Name:"Name 43"}</code></td>
</tr>
<tr>
<td><code>'Age&lt;?',[51]</code></td>
<td>{<code>Age:{$lt:51}}</code></td>
</tr>
<tr>
<td><code>'Age in (1,10,20)'</code></td>
<td>{<code>Age:{$in:[1,10,20]}}</code></td>
</tr>
<tr>
<td><code>'Age in (1,10,20) and ID=?',[10]</code></td>
<td>{<code>Age:{$in:[1,10,20]},_id:10}</code></td>
</tr>
<tr>
<td><code>'Age in (10,20) or ID=?',[30]</code></td>
<td>{<code>$or:[{Age:{$in:[10,20]}},{_id:30}]}</code></td>
</tr>
<tr>
<td><code>'Name like ?',['name 1%']</code></td>
<td>{<code>Name:/^name 1/i}</code></td>
</tr>
<tr>
<td><code>'Name like ?',['name 1']</code></td>
<td>{<code>Name:/^name 1$/i}</code></td>
</tr>
<tr>
<td><code>'Name like ?',['%ame 1%']</code></td>
<td>{<code>Name:/ame 1/i}</code></td>
</tr>
<tr>
<td><code>'Data is null'</code></td>
<td>{<code>Data:null}</code></td>
</tr>
<tr>
<td><code>'Data is not null'</code></td>
<td>{<code>Data:{$ne:null}}</code></td>
</tr>
<tr>
<td><code>'Age&lt;? limit 10',[51]</code></td>
<td>{<code>Age:{$lt:51}}</code> + limit 10</td>
</tr>
<tr>
<td><code>'Age in (10,20) or ID=? order by ID desc',[30]</code></td>
<td>{<code>$query:{$or:[{Age:{$in:[10,20]}},{_id:30}]},$orderby:{_id:-1}}</code></td>
</tr>
<tr>
<td><code>'order by Name'</code></td>
<td>{} + client side text sort by <code>Name</code></td>
</tr>
<tr>
<td><code>'Age in (1,10,20) and IntegerDynArrayContains(Ints,?)',[10])</code></td>
<td>{<code>Age:{$in:[1,10,20]},Ints:{$in:[10]}}</code></td>
</tr>
<tr>
<td><code>Distinct(Age),max(RowID) as first,count(Age) as countgroup by age</code></td>
<td>{<code>$group:{_id:"$Age",f1:{$max:"$_id"},f2:{$sum:1}}},{$project:{_id:0,"Age":"$_id","first":"$f1","count":"$f2"}}</code></td>
</tr>
<tr>
<td><code>min(RowID),max(RowID),Count(RowID)</code></td>
<td>{<code>$group:{_id:null,f0:{$min:"$_id"},f1:{$max:"$_id"},f2:{$sum:1}}},{$project:{_id:0,"min(RowID)":"$f0","max(RowID)":"$f1","Count(RowID)":"$f2"}}</code></td>
</tr>
<tr>
<td><code>min(RowID) as a,max(RowID)+1 as b,Count(RowID) as c</code></td>
<td>{<code>$group:{_id:null,f0:{$min:"$_id"},f1:{$max:"$_id"},f2:{$sum:1}}},{$project:{_id:0,"a":"$f0","b":{$add:["$f1",1]},"c":"$f2"}}</code></td>
</tr>
</tbody>
</table>
<p>  注意括号和混合<code>AND</code> <code>OR</code>表达式还没有处理。通过直接使用<code>TMongoCollection</code>方法，您总是可以执行任何复杂的NoSQL查询（例如使用聚合函数或Map/Reduce模式）。</p>
<p>  但是对于大多数业务代码，mORMot允许在常规SQL数据库或NoSQL引擎之间共享相同的代码。您不需要学习MongoDB查询语法：ODM将根据所运行的数据库引擎，为您计算正确的表达式。</p>
<h3 id="toc_12">9.2.5. 批处理模式<a class="vnote-anchor" href="#toc_12" data-anchor-icon="#"></a></h3>
<p>  除了单独的CRUD操作，MongoDB还可以使用批处理模式添加或删除文档。</p>
<p>  您可以编写与任何SQL后端完全相同的代码：</p>
<pre><code class="lang-pascal hljs">  Client.BatchStart(TSQLORM);
  R := TSQLORM.Create;
  <span class="hljs-keyword">try</span>
    <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span> <span class="hljs-keyword">to</span> COLL_COUNT <span class="hljs-keyword">do</span> <span class="hljs-keyword">begin</span>
      R.<span class="hljs-keyword">Name</span> := <span class="hljs-string">'Name '</span>+Int32ToUTF8(i);
      R.Age := i;
      R.Date := <span class="hljs-number">1.0</span>*(<span class="hljs-number">30000</span>+i);
      R.Value := _ObjFast([<span class="hljs-string">'num'</span>,i]);
      R.Ints := <span class="hljs-keyword">nil</span>;
      R.DynArray(<span class="hljs-number">1</span>).Add(i);
      assert(Client.BatchAdd(R,True)&gt;=<span class="hljs-number">0</span>);
    <span class="hljs-keyword">end</span>;
  <span class="hljs-keyword">finally</span>
    R.Free;
  <span class="hljs-keyword">end</span>;
  assert(Client.BatchSend(IDs)=HTTP_SUCCESS);
</code></pre>
<p>  或使用删除：</p>
<pre><code class="lang-pascal hljs">  Client.BatchStart(TSQLORM);
  <span class="hljs-keyword">for</span> i := <span class="hljs-number">5</span> <span class="hljs-keyword">to</span> COLL_COUNT <span class="hljs-keyword">do</span>
    <span class="hljs-keyword">if</span> i <span class="hljs-keyword">mod</span> <span class="hljs-number">5</span>=<span class="hljs-number">0</span> <span class="hljs-keyword">then</span>
      assert(fClient.BatchDelete(i)&gt;=<span class="hljs-number">0</span>);
  assert(Client.BatchSend(IDs)=HTTP_SUCCESS);
</code></pre>
<p>  对于单独的添加/删除操作，速度优势可能非常大，甚至在本地MongoDB服务器上也是如此。我们将看到一些基准数据。</p>
<h3 id="toc_13">9.2.6. ORM / ODM性能<a class="vnote-anchor" href="#toc_13" data-anchor-icon="#"></a></h3>
<p>  您可以查看<a href="">数据访问基准测试</a>，比较MongoDB作为ORM类的后端。</p>
<p>  对于外部SQL引擎，它具有非常高的速度、较低的CPU消耗，并且在使用上几乎没有区别。我们将<code>BatchAdd()</code>和<code>BatchDelete()</code>方法结合在一起，以利用MongoDB的BULK进程，避免了进程中大部分的内存分配。</p>
<p>  以下是从<code>MongoDBTests.dpr</code>示例中提取的一些数字。它反映了ORM/ODM的性能，取决于是否使用写回执模式:</p>
<pre><code class="lang-sh hljs">2. ORM

 2.1. ORM with acknowledge:
  - Connect to <span class="hljs-built_in">local</span> server: 6 assertions passed  18.65ms
  - Insert: 5,002 assertions passed  521.25ms
     5000 rows inserted <span class="hljs-keyword">in</span> 520.65ms i.e. 9603/s, aver. 104us, 2.9 MB/s
  - Insert <span class="hljs-keyword">in</span> batch mode: 5,004 assertions passed  65.37ms
     5000 rows inserted <span class="hljs-keyword">in</span> 65.07ms i.e. 76836/s, aver. 13us, 8.4 MB/s
  - Retrieve: 45,001 assertions passed  640.95ms
     5000 rows retrieved <span class="hljs-keyword">in</span> 640.75ms i.e. 7803/s, aver. 128us, 2.1 MB/s
  - Retrieve all: 40,001 assertions passed  20.79ms
     5000 rows retrieved <span class="hljs-keyword">in</span> 20.33ms i.e. 245941/s, aver. 4us, 27.1 MB/s
  - Retrieve one with <span class="hljs-built_in">where</span> clause: 45,410 assertions passed  673.01ms
     5000 rows retrieved <span class="hljs-keyword">in</span> 667.17ms i.e. 7494/s, aver. 133us, 2.0 MB/s
  - Update: 40,002 assertions passed  681.31ms
     5000 rows updated <span class="hljs-keyword">in</span> 660.85ms i.e. 7565/s, aver. 132us, 2.4 MB/s
  - Blobs: 125,003 assertions passed  2.16s
     5000 rows updated <span class="hljs-keyword">in</span> 525.97ms i.e. 9506/s, aver. 105us, 2.4 MB/s
  - Delete: 38,003 assertions passed  175.86ms
     1000 rows deleted <span class="hljs-keyword">in</span> 91.37ms i.e. 10944/s, aver. 91us, 2.3 MB/s
  - Delete <span class="hljs-keyword">in</span> batch mode: 33,003 assertions passed  34.71ms
     1000 rows deleted <span class="hljs-keyword">in</span> 14.90ms i.e. 67078/s, aver. 14us, 597 KB/s
  Total failed: 0 / 376,435  - ORM with acknowledge PASSED  5.00s

 2.2. ORM without acknowledge:
  - Connect to <span class="hljs-built_in">local</span> server: 6 assertions passed  16.83ms
  - Insert: 5,002 assertions passed  179.79ms
     5000 rows inserted <span class="hljs-keyword">in</span> 179.15ms i.e. 27908/s, aver. 35us, 3.9 MB/s
  - Insert <span class="hljs-keyword">in</span> batch mode: 5,004 assertions passed  66.30ms
     5000 rows inserted <span class="hljs-keyword">in</span> 31.46ms i.e. 158891/s, aver. 6us, 17.5 MB/s
  - Retrieve: 45,001 assertions passed  642.05ms
     5000 rows retrieved <span class="hljs-keyword">in</span> 641.85ms i.e. 7789/s, aver. 128us, 2.1 MB/s
  - Retrieve all: 40,001 assertions passed  20.68ms
     5000 rows retrieved <span class="hljs-keyword">in</span> 20.26ms i.e. 246718/s, aver. 4us, 27.2 MB/s
  - Retrieve one with <span class="hljs-built_in">where</span> clause: 45,410 assertions passed  680.99ms
     5000 rows retrieved <span class="hljs-keyword">in</span> 675.24ms i.e. 7404/s, aver. 135us, 2.0 MB/s
  - Update: 40,002 assertions passed  231.75ms
     5000 rows updated <span class="hljs-keyword">in</span> 193.74ms i.e. 25807/s, aver. 38us, 3.6 MB/s
  - Blobs: 125,003 assertions passed  1.44s
     5000 rows updated <span class="hljs-keyword">in</span> 150.58ms i.e. 33202/s, aver. 30us, 2.6 MB/s
  - Delete: 38,003 assertions passed  103.57ms
     1000 rows deleted <span class="hljs-keyword">in</span> 19.73ms i.e. 50668/s, aver. 19us, 2.4 MB/s
  - Delete <span class="hljs-keyword">in</span> batch mode: 33,003 assertions passed  47.50ms
     1000 rows deleted <span class="hljs-keyword">in</span> 364us i.e. 2747252/s, aver. 0us, 23.4 MB/s
  Total failed: 0 / 376,435  - ORM without acknowledge PASSED  3.44s
</code></pre>
<p>  对于直接的MongoDB访问，<code>wcUnacknowledged</code>不能用于生产环境，但在某些特定场景中可能非常有用。正如预期的那样，读取过程不受写关回执模式设置的影响。</p>

    </div>
</div>
</div>

<div id="container-floating" style="display:none;" class="d-none d-md-block d-xl-block">
    <div id="floating-button" onclick="toggleMore()">
        <p id="floating-more" class="more">&gt;</p>
    </div>
</div>

<!--
<div class="footer" id="vnote-footer">
    <p>Generated by <em><a href="https://tamlok.github.io/vnote/">VNote</a></em>.</p>
</div>
-->
</body>
</html>
